
package keul.planner.entities;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.OrderColumn;
import javax.persistence.Transient;
import org.jgrapht.graph.DefaultDirectedWeightedGraph;
import org.jgrapht.graph.DefaultWeightedEdge;
import org.jgrapht.graph.DirectedWeightedSubgraph;
import org.jgrapht.graph.SimpleWeightedGraph;
import org.jgrapht.graph.Subgraph;

/**
 * Die Line ist eine Vorlage für eine Zuglinie. Es wird in einer Liste
 * abzufahrende Stationen abgelegt. Ein Array aus int's hält die Abfahrts-
 * zeiten in Minuten von 0:00 ausgehend und eine Durchschnittsgeschwindigkeit.
 * -Name
 * -Id
 * @author kev
 */
@Entity
public class Line implements Serializable {
  
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @ManyToMany
    @OrderColumn
    private List<Station> stops;
    private String name;
    private int[] departure;
    private double averageVelocity;
    @Transient
    private DirectedWeightedSubgraph route;
    
    
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public List<Station> getStops() {
        return stops;
    }
    public void setStops(List<Station> stops) {
        this.stops = stops;
    }
    public void setStops(Station[] stops) {
        this.stops.addAll(Arrays.asList(stops));
    }
    public int[] getDeparture() {
        return departure;
    }
    public void setDeparture(int[] departure) {
        this.departure = departure;
    }
    public double getAverageVelocity() {
        return averageVelocity;
    }
    public void setAverageVelocity(double averageVelocity) {
        this.averageVelocity = averageVelocity;
    }
    
    
    public Line() {
        stops = new ArrayList<Station>(); 
    }
    
    /**
     * Die übergebene Uhrzeit (in Minuten) und Station erlauben die
     * Rückgabe der nächsten Abfahrtszeit für den angefragten Bahnhof.
     * @param desiredStation
     * @param currentTime
     * @return 
     */
    public int getNextDepartureTimeForCertainStationOnLine(Station desiredStation, int currentTime) { //in minutes!
        double distance = calculateDistanceToStartStation(desiredStation);
        int minutes = (int)(distance/averageVelocity*60);
        for (int i : departure) {
            if (i+minutes>=currentTime)
                return i+minutes;
        }
        return departure[0]+minutes;
    }     
    
    
    /**
     * Gibt die Distanz in Kilometern (als double) zurück, die
     * zwischen der angegeben Station und dem Abfahrtsbahnhof liegen.
     */
    public double calculateDistanceToStartStation(Station station) {
        double distance = 0;
        int currentIndex = stops.indexOf(station);
        while (currentIndex>0) {
            distance += Station.computeGeoDistance(stops.get(currentIndex), stops.get(currentIndex-1));
            currentIndex--;
        }
        return distance;  
    }
    
    /**
     * Den Abstand zur vorhergehenden Station, von der angegebenen Station
     * ausgehend berechnet und zurückgegeben.
     * @param station
     * @return 
     */
    public double calculateDistanceToPreviousStation(Station station) {
        double distance = 0;
        int currentIndex = stops.indexOf(station);
        if (currentIndex>0) {
            distance = Station.computeGeoDistance(stops.get(currentIndex), stops.get(currentIndex-1));
        }
        return distance;        
    }    
    
    /**
     * Den zeitlichen Abstand zur vorhergehenden Station, von der 
     * Station ausgehend berechnet und zurückgegeben.
     * @param station
     * @return 
     */    
    public double calculateMinutesToPreviousStation(Station station) {
        double distance = calculateDistanceToPreviousStation(station);
        return (distance/averageVelocity*60);           
    }
    
    /**
     * Ein String-Array mit 24 Fächern enthält den Fahrplan für
     * die jeweilige Station
     * @param station
     * @return 
     */
    public String[] getReadableDepartureTimes(Station station) {
        int offset = (int)(calculateDistanceToStartStation(station)/averageVelocity*60);
        String[] timeStrings = new String[24];
        int currentHour=0;
        for (int i = 0; i < 24; i++) {
            timeStrings[i]=String.format("%02d  ", i);
        }
        for (int time : departure) {
            time+=offset;
            if (time>=currentHour*60 && time<(currentHour+1)*60)
                timeStrings[currentHour%24] += String.format("%02d  ", (time%60)) + " ";
            else {
                while (!(time>=currentHour*60) || !(time<(currentHour+1)*60))
                    currentHour++;
                timeStrings[currentHour%24] += String.format("%02d ", (time%60)) + " ";
            }
        }   
        return timeStrings;
    }
    
    /**
     * Die Strecke der Linie wird in einen Subgraph gefasst
     * und zurückgegeben.
     * @return 
     */
    public Subgraph getGraph(SimpleWeightedGraph net) {
        HashSet<Station> stopsSet = new HashSet<Station>(stops);
        Subgraph subgraph = new Subgraph(net, stopsSet);
        return subgraph;
    }
    
    /**
     * Hiermit kann abgefragt werden, ob eine beliebige Station
     * auf der Strecke dieser Linie liegt.
     * @param certainStation
     * @return 
     */
    public boolean isLineServing(Station certainStation) {
        for (Station station : stops) {
            if (station.equals(certainStation))
                return true;
        }
        return false;
    }
    /**
     * Hiermit kann abgefragt werden, ob die Linie zwei Stationen,
     * auch unter Berücksichtigung des Streckenverlaufs, bedient.
     * @param start
     * @param destination
     * @return 
     */
    public boolean isLineDirectingTo(Station start, Station destination) {
        if ( isLineServing(start) && 
             isLineServing(destination) && 
             stops.indexOf(start) < stops.indexOf(destination)) {
                return true;
        }
        return false;
    }


    
    
    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    
    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Line)) {
            return false;
        }
        Line other = (Line) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    
    @Override
    public String toString() {
        return name;
    }
    /**
     * ausführliche toString()-Methode
     * @return 
     */
    public String oldToString() {
        StringBuffer sb = new StringBuffer();
        sb      .append("kev.entities.TrainRoute[ id=") .append(id)     
                .append(", name=\"")                    .append(name)   
                .append(", stops[]={");
        int iCount=stops.size();
        for (Station station : stops) {
            sb.append(station.getName());
            if(--iCount!=0)
                sb.append(", ");            
        }
        sb.append("}, departures[]={");
        iCount=departure.length;
        for (int time : departure) {
            sb.append(time);
            if(--iCount!=0)
                sb.append(", ");            
        }
        sb.append("} ] \n");
        return sb.toString();
    }
    
    
    /**
     * Instanziiert den Subgraph mit Hilfe von dem Gesamt-Graph und den Stations.
     * @param net Streckennetz (DefaultDirectedWeightedGraph)
     * @param stations ArrayList von Stations
     */
    public void putVerticesAndEdgesFromSuperGraph(DefaultDirectedWeightedGraph ddwg) {
        Set<Station> vertices = new HashSet<Station>();
        Set<DefaultWeightedEdge> edges = new HashSet<DefaultWeightedEdge>();
        System.out.println("Route Konstruktor:");
        for (int i = 0; i < stops.size(); i++) {
            vertices.add(stops.get(i));
            if (i < stops.size() - 1) {
                edges.add((DefaultWeightedEdge) ddwg.getEdge(stops.get(i), stops.get(i + 1)));
            }
        }
        this.route = new DirectedWeightedSubgraph(ddwg, vertices, edges);
    }
    
    
}
